/*! backbone.routefilter - v0.1.0 - 2012-09-10
* https://github.com/boazsender/backbone.routefilter
* Copyright (c) 2012 Boaz Sender; Licensed MIT */

/*! backbone.routefilter - v0.1.0 - 2012-08-29
* https://github.com/boazsender/backbone.routefilter
* Copyright (c) 2012 Boaz Sender; Licensed MIT */
/*global Backbone:false, _: false, console: false*/

define(function (require) {

    var Backbone = require("backbone");
    var _ = require("underscore");

    // Save a reference to the original route method to be called
    // after we pave it over.
    var originalRoute = Backbone.Router.prototype.route;

    // Create a reusable no operation func for the case where a before
    // or after filter is not set. Backbone or Underscore should have
    // a global one of these in my opinion.
    var nop = function () {};

    // Extend the router prototype with a default before function,
    // a default after function, and a pave over of _bindRoutes.
    _.extend(Backbone.Router.prototype, {

        // Add default before filter.
        before: nop,

        // Add default after filter.
        after: nop,

        // Pave over Backbone.Router.prototype.route, the public method used
        // for adding routes to a router instance on the fly, and the
        // method which backbone uses internally for binding routes to handlers
        // on the Backbone.history singleton once it's instantiated.
        route: function (route, name, callback) {

            // If there is no callback present for this route, then set it to
            // be the name that was set in the routes property of the constructor,
            // or the name arguement of the route method invocation. This is what
            // Backbone.Router.route already does. We need to do it again,
            // because we are about to wrap the callback in a function that calls
            // the before and after filters as well as the original callback that
            // was passed in.
            if (!callback) {
                callback = this[name];
            }

            // Create a new callback to replace the original callback that calls
            // the before and after filters as well as the original callback
            // internally.
            var wrappedCallback = _.bind(function () {
                var args = [];

                _.each(arguments, function (item) {
                    args.push(item);
                });

                // Call the before filter and if it returns false, run the
                // route's original callback, and after filter. This allows
                // the user to return false from within the before filter
                // to prevent the original route callback and after
                // filter from running.
                if (this.before.call(this, route, name, args) === false) {
                    return;
                }

                // If the callback exists, then call it. This means that the before
                // and after filters will be called whether or not an actual
                // callback function is supplied to handle a given route.
                if (callback) {
                    callback.call(this, route, name, args);
                }

                // Call the after filter.
                this.after.call(this, route, name, args);

            }, this);

            // Call our original route, replacing the callback that was originally
            // passed in when Backboun.Router.route was invoked with our wrapped
            // callback that calls the before and after callbacks as well as the
            // original callback.
            return originalRoute.call(this, route, name, wrappedCallback);
        }
    });

});
